package com.loveprogrammer.javassist;

/**
 * @ClassName GenerateClazzMethod
 * @Description TODO
 * @Author admin
 * @Date 2023/11/17 16:28
 * @Version 1.0
 */

import javassist.*;
import javassist.bytecode.*;
import javassist.util.HotSwapper;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 去修改一个正在执行中的类里面的方法内容。也就是在运行时重新加载类信息
 *
 */
/**
 * 公众号：bugstack虫洞栈
 * 博客栈：https://bugstack.cn - 沉淀、分享、成长，让自己和他人都能有所收获！
 * 本专栏是小傅哥多年从事一线互联网Java开发的学习历程技术汇总，旨在为大家提供一个清晰详细的学习教程，侧重点更倾向编写Java核心内容。如果能为您提供帮助，请给予支持(关注、点赞、分享)！
 */
public class GenerateClazzMethod extends ClassLoader {

    public static void main(String[] args) throws Exception {

        ClassPool pool = ClassPool.getDefault();

        CtClass ctClass = pool.get(ApiTest.class.getName());
        ctClass.replaceClassName("ApiTest","ApiTest02");
        // 通过类名获取类的信息，同时这里可以把类名进行替换。它也包括类里面一些其他获取属性的操作，比如；ctClass.getSimpleName()、ctClass.getAnnotations() 等。
        String clazzName = ctClass.getName();

        // 获取方法 这里我们是指定了某个方法，还可以通过ctClass.getMethods()来进行遍历
        CtMethod ctMethod = ctClass.getDeclaredMethod("strToInt");
        String methodName = ctMethod.getName();
        // 获取方法信息
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        boolean isStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) != 0;

        // 方法的入参信息
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        // 入参的名称
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        // 入参的类型
        CtClass[] parameterTypes = ctMethod.getParameterTypes();

        // 出参信息
        CtClass returnType = ctMethod.getReturnType();
        String returnTypeName = returnType.getName();

        System.out.println("类名：" + clazzName);
        System.out.println("方法：" + methodName);
        System.out.println("类型：" + (isStatic ? "静态方法" : "非静态方法"));
        System.out.println("描述：" + methodInfo.getDescriptor());
        System.out.println("入参[名称]：" + attr.variableName(1) + "，" + attr.variableName(2));
        System.out.println("入参[类型]：" + parameterTypes[0].getName() + "，" + parameterTypes[1].getName());
        System.out.println("出参[类型]：" + returnTypeName);

        int parameterSize = isStatic ? attr.tableLength() : attr.tableLength() - 1; // 静态类型取值
        List<String> parameterNameList = new ArrayList<>(parameterSize);            // 入参名称
        List<String> parameterTypeList = new ArrayList<>(parameterSize);            // 入参类型
        StringBuilder parameters = new StringBuilder();                             // 参数组装；$1、$2...，$$可以获取全部，但是不能放到数组初始化

        for (int i = 0; i < parameterSize; i++) {
            parameterNameList.add(attr.variableName(i + (isStatic ? 0 : 1))); // 静态类型去掉第一个this参数
            parameterTypeList.add(parameterTypes[i].getName());
            if (i + 1 == parameterSize) {
                parameters.append("$").append(i + 1);
            } else {
                parameters.append("$").append(i + 1).append(",");
            }
        }

        // 方法：生成方法唯一标识ID
        int idx = Monitor.generateMethodId(clazzName, methodName, parameterNameList, parameterTypeList, returnTypeName);

        // 字节码插桩添加进入方法的时间
        // 定义属性
        ctMethod.addLocalVariable("startNanos", CtClass.longType);
        // 方法前加强
        ctMethod.insertBefore("{ startNanos = System.nanoTime(); }");

        // 字节码添加入参输出
        // 定义属性
        ctMethod.addLocalVariable("parameterValues", pool.get(Object[].class.getName()));
        // 方法前加强

        ctMethod.insertBefore("{ parameterValues = new Object[]{" + parameters.toString() + "}; }");

        // 字节码调用监控方法
        // 方法后加强
        ctMethod.insertAfter("{ com.loveprogrammer.javassist.Monitor.point(" + idx + ", startNanos, parameterValues, $_);}", false); // 如果返回类型非对象类型，$_ 需要进行类型转换

        // 方法后加强
        ctMethod.insertAfter("{ com.loveprogrammer.javassist.Monitor.point(" + idx + ", startNanos, parameterValues, $_);}", false); // 如果返回类型非对象类型，$_ 需要进行类型转换

        // 方法；添加TryCatch
        ctMethod.addCatch("{ com.loveprogrammer.javassist.Monitor.point(" + idx + ", $e); throw $e; }", ClassPool.getDefault().get("java.lang.Exception"));   // 添加异常捕获

        // 输出类的内容
        ctClass.writeFile();

        // 测试调用
        byte[] bytes = ctClass.toBytecode();
        Class<?> clazzNew = new GenerateClazzMethod().defineClass("com.loveprogrammer.javassist.ApiTest", bytes, 0, bytes.length);

        // 反射获取 main 方法
        Method method = clazzNew.getMethod("strToInt", String.class, String.class);
        Object obj_01 = method.invoke(clazzNew.newInstance(), "1", "2");
        System.out.println("正确入参：" + obj_01);

        Object obj_02 = method.invoke(clazzNew.newInstance(), "a", "b");
        System.out.println("异常入参：" + obj_02);
    }

}


